import 'package:flutter/material.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'package:serverpod_auth_core_flutter/serverpod_auth_core_flutter.dart';

import 'google_auth_controller.dart';
import 'common/button.dart';
import 'common/style.dart';
import 'native/button.dart';
import 'web/button.dart';

export 'native/button.dart';
export 'web/button.dart';

/// A widget that provides Google Sign-In functionality for all platforms.
///
/// The widget can manage its own authentication state, or you can provide an
/// external [controller] for advanced use cases like sharing state across
/// multiple widgets or integrating with state management solutions.
///
/// When [controller] is not provided, you must supply [client] and optionally
/// [onAuthenticated] and [onError] callbacks. When [controller] is provided,
/// those callbacks are handled by the controller itself.
///
/// Example with managed state:
/// ```
/// GoogleSignInWidget(
///   client: client,
///   onAuthenticated: () => Navigator.push(...),
///   onError: (error) => showSnackBar(...),
/// )
/// ```
///
/// Example with external controller:
/// ```
/// final controller = GoogleAuthController(
///   client: client,
///   onAuthenticated: ...,
/// );
///
/// GoogleSignInWidget(
///   controller: controller,
/// )
/// ```
class GoogleSignInWidget extends StatefulWidget {
  /// Controls the authentication state and behavior.
  ///
  /// If null, the widget creates and manages its own [GoogleAuthController].
  /// In this case, [client] must be provided.
  ///
  /// If provided, the widget uses this controller instead of creating one,
  /// and [client], [onAuthenticated], and [onError] are ignored.
  final GoogleAuthController? controller;

  /// The Serverpod client instance.
  ///
  /// Required when [controller] is null, ignored otherwise.
  final ServerpodClientShared? client;

  /// Callback when authentication is successful.
  ///
  /// Ignored when [controller] is provided.
  final VoidCallback? onAuthenticated;

  /// Callback when an error occurs during authentication.
  ///
  /// Ignored when [controller] is provided.
  final Function(Object error)? onError;

  /// Whether to attempt to authenticate the user automatically using the
  /// `attemptLightweightAuthentication` method after the widget is initialized.
  ///
  /// The amount of allowable UI is up to the platform to determine, but it
  /// should be minimal. Possible examples include FedCM on the web, and One Tap
  /// on Android. Platforms may even show no UI, and only sign in if a previous
  /// sign-in is being restored. This method is intended to be called as soon
  /// as the application needs to know if the user is signed in, often at
  /// initial launch.
  final bool attemptLightweightSignIn;

  /// Scopes to request from Google.
  ///
  /// The default scopes are `email` and `profile`, which will give access to
  /// retrieving the profile name and picture automatically.
  final List<String> scopes;

  /// The button type: icon, or standard button.
  final GSIButtonType type;

  /// The button theme.
  ///
  /// For example, filledBlue or filledBlack.
  final GSIButtonTheme theme;

  /// The button size.
  ///
  /// For example, small or large.
  final GSIButtonSize size;

  /// The button text.
  ///
  /// For example "Sign in with Google" or "Sign up with Google".
  final GSIButtonText text;

  /// The button shape.
  ///
  /// For example, rectangular or circular.
  final GSIButtonShape shape;

  /// The Google logo alignment: left or center.
  final GSIButtonLogoAlignment logoAlignment;

  /// The minimum button width, in pixels.
  ///
  /// The maximum width is 400 pixels.
  final double minimumWidth;

  /// A function to generate the button text based on the current configuration.
  ///
  /// This function is only used on native platforms. On the web, the button
  /// text is generated by the Google Sign-In library. Use [text] and [locale]
  /// instead to customize the button text.
  final String Function({bool isLoading})? getButtonText;

  /// The pre-set locale of the button text.
  ///
  /// If not set, the device's default locale is used. This parameter only
  /// applies to the web platform. On native, use [getButtonText] instead.
  ///
  /// Different users might see different versions of localized buttons,
  /// possibly with different sizes.
  final String? locale;

  /// A wrapper function to the rendered button to ensure style consistency.
  ///
  /// This wrapper ensures the consistency of the rendered button with the rest
  /// of the application. Since the render configuration is done through enum
  /// values, the wrapper will be called with a [GoogleSignInStyle] object that
  /// translates the enum values to actual style properties. The [Widget] is the
  /// rendered Google button that should be wrapped.
  ///
  /// Be mindful that creating the button with no wrapper will also result in a
  /// dangling "Getting ready..." text that is returned while the iFrame is
  /// being built.
  final Widget Function({
    required GoogleSignInStyle style,
    required Widget child,
    required VoidCallback? onPressed,
  })?
  buttonWrapper;

  /// Creates a Google Sign-In widget.
  const GoogleSignInWidget({
    this.controller,
    this.client,
    this.onAuthenticated,
    this.onError,
    this.attemptLightweightSignIn = true,
    this.scopes = GoogleAuthController.defaultScopes,
    this.type = GSIButtonType.standard,
    this.theme = GSIButtonTheme.outline,
    this.size = GSIButtonSize.large,
    this.text = GSIButtonText.continueWith,
    this.shape = GSIButtonShape.pill,
    this.logoAlignment = GSIButtonLogoAlignment.center,
    this.minimumWidth = 240,
    this.getButtonText,
    this.locale,
    this.buttonWrapper = GoogleSignInBaseButton.wrapAsOutline,
    super.key,
  }) : assert(
         (controller == null || client == null),
         'Either controller or client must be provided, but not both. When '
         'passing a controller, only the `webButton` parameter is used. ',
       );

  @override
  State<GoogleSignInWidget> createState() => _GoogleSignInWidgetState();
}

class _GoogleSignInWidgetState extends State<GoogleSignInWidget> {
  late final GoogleAuthController _controller;

  @override
  void initState() {
    super.initState();
    _controller =
        widget.controller ??
        GoogleAuthController(
          client: widget.client!,
          onAuthenticated: widget.onAuthenticated,
          onError: widget.onError,
          attemptLightweightSignIn: widget.attemptLightweightSignIn,
          scopes: widget.scopes,
        );
    _controller.addListener(_onControllerStateChanged);
  }

  @override
  void dispose() {
    _controller.removeListener(_onControllerStateChanged);
    if (widget.controller == null) {
      _controller.dispose();
    }
    super.dispose();
  }

  /// Rebuild when controller state changes
  void _onControllerStateChanged() => setState(() {});

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisSize: MainAxisSize.min,
      crossAxisAlignment: CrossAxisAlignment.stretch,
      children: [
        if (GoogleSignIn.instance.supportsAuthenticate())
          GoogleSignInNativeButton(
            onPressed: _controller.signIn,
            isLoading: _controller.isLoading,
            isDisabled: !_controller.isInitialized || _controller.isLoading,
            type: widget.type,
            theme: widget.theme,
            size: widget.size,
            text: widget.text,
            shape: widget.shape,
            logoAlignment: widget.logoAlignment,
            minimumWidth: widget.minimumWidth,
            getButtonText: widget.getButtonText,
            buttonWrapper: widget.buttonWrapper,
          )
        else if (_controller.isInitialized)
          GoogleSignInWebButton(
            type: widget.type,
            theme: widget.theme,
            size: widget.size,
            text: widget.text,
            shape: widget.shape,
            logoAlignment: widget.logoAlignment,
            minimumWidth: widget.minimumWidth,
            locale: widget.locale,
            buttonWrapper: widget.buttonWrapper,
          ),
      ],
    );
  }
}
